﻿using System;

using Geometria.Angulos;

namespace Geometria
{
    /// <summary>
    /// 
    /// </summary>
    public struct Vector : IEquatable<Vector>
    {
        #region Fields

        private double x;
        private double y;
        private double z;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="z"></param>
        public Vector(double x, double y, double z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="origen"></param>
        /// <param name="destino"></param>
        public Vector(Punto origen, Punto destino)
        {
            this.x = destino.X - origen.X;
            this.y = destino.Y - origen.Y;
            this.z = destino.Z - origen.Z;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="destino"></param>
        public Vector(Punto destino)
            : this(Punto.Cero, destino)
        {
        }

        #endregion Constructors

        #region Properties

        public static Vector EjeX
        {
            get
            {
                return new Vector(1.0, 0.0, 0.0);
            }
        }

        public static Vector EjeY
        {
            get
            {
                return new Vector(0.0, 1.0, 0.0);
            }
        }

        public static Vector EjeZ
        {
            get
            {
                return new Vector(0.0, 0.0, 1.0);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public double Longitud
        {
            get
            {
                return Math.Sqrt(X * X + Y * Y + Z * Z);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public double X
        {
            get { return this.x; }
        }

        /// <summary>
        /// 
        /// </summary>
        public double Y
        {
            get { return this.y; }
        }

        /// <summary>
        /// 
        /// </summary>
        public double Z
        {
            get { return this.z; }
        }

        #endregion Properties

        #region Methods

        public static Vector operator *(Vector v1, double factor)
        {
            return Multiply(v1, factor);
        }

        public static Punto operator +(Vector v, Punto p)
        {
            return v.MovePoint(p);
        }

        public static Vector operator +(Vector v1, Vector v2)
        {
            return Add(v1, v2);
        }

        public static Vector operator -(Vector v1, Vector v2)
        {
            return Add(v1, Multiply(v2, -1));
        }

        public static Vector operator /(Vector v1, double factor)
        {
            return Divive(v1, factor);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static Vector Add(Vector v1, Vector v2)
        {
            return new Vector(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
        }

        public static Radian GetAngulo(Vector v1, Vector v2)
        {
            double l1, l2, lescalar;
            double tmp;

            l1 = v1.Longitud;
            l2 = v2.Longitud;
            lescalar = ProductoEscalar(v1, v2);

            tmp = lescalar / (l1 * l2);

            return Math.Acos(tmp);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="factor"></param>
        /// <returns></returns>
        public static Vector Multiply(Vector v1, double factor)
        {
            return new Vector(v1.X * factor, v1.Y * factor, v1.Z * factor);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool Paralelo(Vector v1, Vector v2)
        {
            Vector _v1;
            Vector _v2;
            double producto;

            _v1 = v1.Normalize();
            _v2 = v2.Normalize();

            producto = Vector.ProductoEscalar(_v1, _v2);

            return producto == 1 || producto == -1;
        }

        public static bool Perpendicular(Vector v1, Vector v2)
        {
            return Vector.ProductoEscalar(v1, v2) != 0;
        }

        public static double ProductoEscalar(Vector v1, Vector v2)
        {
            return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z;
        }

        public bool Equals(Vector other)
        {
            return this.X == other.X
                && this.Y == other.Y
                && this.Z == other.Z;
        }

        public Punto MovePoint(Punto p)
        {
            return new Punto(p.X + this.X, p.Y + this.Y, p.Z + this.Z);
        }

        /// <summary>
        /// Genera un vector de longitud 1
        /// </summary>
        /// <returns></returns>
        public Vector Normalize()
        {
            double len;

            len = this.Longitud;

            if (len != 0)
                return new Vector(this.x / len, this.y / len, this.z / len);
            else
                return new Vector();
        }

        private static Vector Divive(Vector v1, double factor)
        {
            if (factor == 0.0)
                throw new DivideByZeroException();

            return Multiply(v1, 1 / factor);
        }

        #endregion Methods
    }
}